import * as THREE from 'three';
import { MeshPhongMaterial } from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import URDFLoader from './URDFLoader.js';

const tempVec2 = new THREE.Vector2();
const emptyRaycast = () => { };

// urdf查看器元素
// 加载并显示URDF格式机器人的3D视图

// 活动
// urdf change:当urdf完成加载和处理时，会发生火灾
// urdf已处理:当urdf完成加载和处理时，会发生火灾
// 几何体已加载:当所有几何体都已完全加载时开火
// 忽略限制更改:当“忽略限制”属性更改时触发
// 角度变化:角度变化时开火
export default
  class URDFViewer extends HTMLElement {
  static get observedAttributes() {
    return ['package', 'urdf', 'up', 'display-shadow', 'ambient-color', 'ignore-limits', 'show-collision'];
  }
  get package() { return this.getAttribute('package') || ''; }
  set package(val) { this.setAttribute('package', val); }

  get urdf() { return this.getAttribute('urdf') || ''; }
  set urdf(val) { this.setAttribute('urdf', val); }

  get ignoreLimits() { return this.hasAttribute('ignore-limits') || false; }
  set ignoreLimits(val) { val ? this.setAttribute('ignore-limits', val) : this.removeAttribute('ignore-limits'); }

  get up() { return this.getAttribute('up') || '+Z'; }
  set up(val) { this.setAttribute('up', val); }

  get displayShadow() { return this.hasAttribute('display-shadow') || false; }
  set displayShadow(val) { val ? this.setAttribute('display-shadow', '') : this.removeAttribute('display-shadow'); }

  get ambientColor() { return this.getAttribute('ambient-color') || '#8ea0a8'; }
  set ambientColor(val) { val ? this.setAttribute('ambient-color', val) : this.removeAttribute('ambient-color'); }

  get autoRedraw() { return this.hasAttribute('auto-redraw') || false; }
  set autoRedraw(val) { val ? this.setAttribute('auto-redraw', true) : this.removeAttribute('auto-redraw'); }

  get noAutoRecenter() { return this.hasAttribute('no-auto-recenter') || false; }
  set noAutoRecenter(val) { val ? this.setAttribute('no-auto-recenter', true) : this.removeAttribute('no-auto-recenter'); }

  get showCollision() { return this.hasAttribute('show-collision') || false; }
  set showCollision(val) { val ? this.setAttribute('show-collision', true) : this.removeAttribute('show-collision'); }

  get jointValues() {

    const values = {};
    if (this.robot) {

      for (const name in this.robot.joints) {

        const joint = this.robot.joints[name];
        values[name] = joint.jointValue.length === 1 ? joint.angle : [...joint.jointValue];

      }

    }

    return values;

  }
  set jointValues(val) { this.setJointValues(val); }

  get angles() {
    return this.jointValues;
  }
  set angles(v) {
    this.jointValues = v;
  }

  /* Lifecycle Functions */
  constructor() {
    super();
    this._requestId = 0;
    this._dirty = false;
    this._loadScheduled = false;
    this.robot = null;
    this.loadMeshFunc = null;
    this.urlModifierFunc = null;

    // 场景设置
    const scene = new THREE.Scene();

    const ambientLight = new THREE.HemisphereLight(this.ambientColor, '#000');
    ambientLight.groundColor.lerp(ambientLight.color, 0.5 * Math.PI);
    ambientLight.intensity = 0.5;
    ambientLight.position.set(0, 1, 0);
    scene.add(ambientLight);

    // 灯光设置
    const dirLight = new THREE.DirectionalLight(0xffffff, Math.PI);
    dirLight.position.set(4, 10, 1);
    dirLight.shadow.mapSize.width = 2048;
    dirLight.shadow.mapSize.height = 2048;
    dirLight.shadow.normalBias = 0.001;
    dirLight.castShadow = true;
    scene.add(dirLight);
    scene.add(dirLight.target);

    // 渲染器设置
    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setClearColor(0xffffff);
    renderer.setClearAlpha(0);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.outputColorSpace = THREE.SRGBColorSpace;

    // 摄像头设置
    const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
    camera.position.z = -10;

    // 世界设置
    const world = new THREE.Object3D();
    scene.add(world);

    const plane = new THREE.Mesh(
      new THREE.PlaneGeometry(40, 40),
      new THREE.ShadowMaterial({ side: THREE.DoubleSide, transparent: true, opacity: 0.25 }),
    );
    plane.rotation.x = -Math.PI / 2;
    plane.position.y = -0.5;
    plane.receiveShadow = true;
    plane.scale.set(10, 10, 10);
    scene.add(plane);

    // 控制设置
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.rotateSpeed = 2.0;
    controls.zoomSpeed = 5;
    controls.panSpeed = 2;
    controls.enableZoom = true;
    controls.enableDamping = false;
    controls.maxDistance = 50;
    controls.minDistance = 0.25;
    controls.addEventListener('change', () => this.recenter());

    this.scene = scene;
    this.world = world;
    this.renderer = renderer;
    this.camera = camera;
    this.controls = controls;
    this.plane = plane;
    this.directionalLight = dirLight;
    this.ambientLight = ambientLight;

    this._setUp(this.up);

    this._collisionMaterial = new MeshPhongMaterial({
      transparent: true,
      opacity: 0.35,
      shininess: 2.5,
      premultipliedAlpha: true,
      color: 0xffbe38,
      polygonOffset: true,
      polygonOffsetFactor: -1,
      polygonOffsetUnits: -1,
    });

    const _renderLoop = () => {

      if (this.parentNode) {

        this.updateSize();

        if (this._dirty || this.autoRedraw) {

          if (!this.noAutoRecenter) {

            this._updateEnvironment();
          }

          this.renderer.render(scene, camera);
          this._dirty = false;

        }

        // 在环境之后更新控件
        // 如果控制被重定向
        this.controls.update();

      }
      this._renderLoopId = requestAnimationFrame(_renderLoop);

    };
    _renderLoop();

  }

  connectedCallback() {
    // 如果元素没有初始化样式，请为其添加初始化样式
    // 已添加
    if (!this.constructor._styletag) {
      const styletag = document.createElement('style');
      styletag.innerHTML =
        `
                ${this.tagName} { display: block; }
                ${this.tagName} canvas {
                    width: 100%;
                    height: 100%;
                }
            `;
      document.head.appendChild(styletag);
      this.constructor._styletag = styletag;
    }

    // 添加渲染器
    if (this.childElementCount === 0) {
      this.appendChild(this.renderer.domElement);
    }
    this.updateSize();
    requestAnimationFrame(() => this.updateSize());
  }

  disconnectedCallback() {
    cancelAnimationFrame(this._renderLoopId);
  }

  attributeChangedCallback(attr, oldval, newval) {
    this._updateCollisionVisibility();
    if (!this.noAutoRecenter) {
      this.recenter();
    }
    switch (attr) {
      case 'package':
      case 'urdf': {
        this._scheduleLoad();
        break;
      }

      case 'up': {
        this._setUp(this.up);
        break;
      }

      case 'ambient-color': {
        this.ambientLight.color.set(this.ambientColor);
        this.ambientLight.groundColor.set('#000').lerp(this.ambientLight.color, 0.5);
        break;
      }

      case 'ignore-limits': {
        this._setIgnoreLimits(this.ignoreLimits, true);
        break;
      }
    }
  }

  /* Public API */
  updateSize() {
    const r = this.renderer;
    const w = this.clientWidth;
    const h = this.clientHeight;
    const currSize = r.getSize(tempVec2);
    if (currSize.width !== w || currSize.height !== h) {
      this.recenter();
    }
    r.setPixelRatio(window.devicePixelRatio);
    r.setSize(w, h, false);
    this.camera.aspect = w / h;
    this.camera.updateProjectionMatrix();

  }

  redraw() {
    this._dirty = true;
  }

  recenter() {
    this._updateEnvironment();
    this.redraw();

  }

  // 使用jointName将关节设置为
  // 角度（度）
  setJointValue(jointName, ...values) {
    if (!this.robot) return;
    if (!this.robot.joints[jointName]) return;
    if (this.robot.joints[jointName].setJointValue(...values)) {
      this.redraw();
      this.dispatchEvent(new CustomEvent('angle-change', { bubbles: true, cancelable: true, detail: jointName }));
    }
  }

  setJointValues(values) {
    for (const name in values) this.setJointValue(name, values[name]);
  }

  /* Private Functions */
  // 将飞机的位置更新为
  // 机器人下方的最低点，并聚焦
  // 场景中央的摄像头
  _updateEnvironment() {
    const robot = this.robot;
    if (!robot) return;
    this.world.updateMatrixWorld();
    const bbox = new THREE.Box3();
    bbox.makeEmpty();
    robot.traverse(c => {
      if (c.isURDFVisual) {
        bbox.expandByObject(c);
      }
    });
    const center = bbox.getCenter(new THREE.Vector3());
    this.controls.target.y = center.y;
    this.plane.position.y = bbox.min.y - 1e-3;
    const dirLight = this.directionalLight;
    dirLight.castShadow = this.displayShadow;
    if (this.displayShadow) {
      // 更新阴影摄影机渲染边界以封装
      // 模型。我们使用边界框的边界球体
      // 简单——这可能更合适。
      const sphere = bbox.getBoundingSphere(new THREE.Sphere());
      const minmax = sphere.radius;
      const cam = dirLight.shadow.camera;
      cam.left = cam.bottom = -minmax;
      cam.right = cam.top = minmax;
      // 更新相机以聚焦在模型的中心，以便
      // 阴影可以包裹它
      const offset = dirLight.position.clone().sub(dirLight.target.position);
      dirLight.target.position.copy(center);
      dirLight.position.copy(center).add(offset);
      cam.updateProjectionMatrix();
    }
  }

  _scheduleLoad() {
    // 如果我们当前的模型已经符合要求
    // 或者已经提前装载
    if (this._prevload === `${this.package}|${this.urdf}`) return;
    this._prevload = `${this.package}|${this.urdf}`;

    // 如果我们已经在等货物了，那就早点出发
    if (this._loadScheduled) return;
    this._loadScheduled = true;

    if (this.robot) {
      this.robot.traverse(c => c.dispose && c.dispose());
      this.robot.parent.remove(this.robot);
      this.robot = null;
    }

    requestAnimationFrame(() => {
      this._loadUrdf(this.package, this.urdf);
      this._loadScheduled = false;
    });
  }

  // 观察包装和urdf字段，并加载机器人模型。
  // 这应该只从_scheduleLoad调用_oy_，因为
  // 确保当前机器人已被移除
  _loadUrdf(pkg, urdf) {
    this.dispatchEvent(new CustomEvent('urdf-change', { bubbles: true, cancelable: true, composed: true }));
    if (urdf) {
      // 跟踪此请求并做出
      // 确保它不会被覆盖
      // 下一个
      this._requestId++;
      const requestId = this._requestId;
      const updateMaterials = mesh => {
        mesh.traverse(c => {
          if (c.isMesh) {
            c.castShadow = true;
            c.receiveShadow = true;
            if (c.material) {
              const mats =
                (Array.isArray(c.material) ? c.material : [c.material])
                  .map(m => {
                    if (m instanceof THREE.MeshBasicMaterial) {
                      m = new THREE.MeshPhongMaterial();
                    }

                    if (m.map) {
                      m.map.colorSpace = THREE.SRGBColorSpace;
                    }

                    return m;

                  });
              c.material = mats.length === 1 ? mats[0] : mats;
            }
          }
        });
      };

      if (pkg.includes(':') && (pkg.split(':')[1].substring(0, 2)) !== '//') {
        // 例如，pkg=“pkg_name:path/to/pkg_name，pk2:path2/to/pk2”}

        // 将pkg（s）转换为地图。例如。
        // {“pkg_name”:“path/to/pkg_name”，
        // “pk2”:“path2/to/pk2”}
        pkg = pkg.split(',').reduce((map, value) => {
          const split = value.split(/:/).filter(x => !!x);
          const pkgName = split.shift().trim();
          const pkgPath = split.join(':').trim();
          map[pkgName] = pkgPath;
          return map;
        }, {});
      }

      let robot = null;
      const manager = new THREE.LoadingManager();
      manager.onLoad = () => {
        // 如果有另一个请求来加载新的
        // 机器人，然后忽略这个
        if (this._requestId !== requestId) {
          robot.traverse(c => c.dispose && c.dispose());
          return;
        }

        this.robot = robot;
        this.world.add(robot);
        updateMaterials(robot);
        this._setIgnoreLimits(this.ignoreLimits);
        this._updateCollisionVisibility();
        this.dispatchEvent(new CustomEvent('urdf-processed', { bubbles: true, cancelable: true, composed: true }));
        this.dispatchEvent(new CustomEvent('geometry-loaded', { bubbles: true, cancelable: true, composed: true }));
        this.recenter();
      };

      if (this.urlModifierFunc) {
        manager.setURLModifier(this.urlModifierFunc);
      }

      const loader = new URDFLoader(manager);
      loader.packages = pkg;
      loader.loadMeshCb = this.loadMeshFunc;
      loader.fetchOptions = { mode: 'cors', credentials: 'same-origin' };
      loader.parseCollision = true;
      loader.load(urdf, model => robot = model);
    }
  }

  _updateCollisionVisibility() {
    const showCollision = this.showCollision;
    const collisionMaterial = this._collisionMaterial;
    const robot = this.robot;
    if (robot === null) return;
    const colliders = [];
    robot.traverse(c => {
      if (c.isURDFCollider) {
        c.visible = showCollision;
        colliders.push(c);
      }
    });

    colliders.forEach(coll => {
      coll.traverse(c => {
        if (c.isMesh) {
          c.raycast = emptyRaycast;
          c.material = collisionMaterial;
          c.castShadow = false;
        }
      });
    });
  }

  // 观察坐标系并更新
  // 旋转场景以匹配
  _setUp(up) {
    if (!up) up = '+Z';
    up = up.toUpperCase();
    const sign = up.replace(/[^-+]/g, '')[0] || '+';
    const axis = up.replace(/[^XYZ]/gi, '')[0] || 'Z';
    const PI = Math.PI;
    const HALFPI = PI / 2;
    if (axis === 'X') this.world.rotation.set(0, 0, sign === '+' ? HALFPI : -HALFPI);
    if (axis === 'Z') this.world.rotation.set(sign === '+' ? -HALFPI : HALFPI, 0, 0);
    if (axis === 'Y') this.world.rotation.set(sign === '+' ? 0 : PI, 0, 0);
  }

  // 更新当前机器人的角度以忽略
  // 是否有关节限制
  _setIgnoreLimits(ignore, dispatch = false) {
    if (this.robot) {
      Object
        .values(this.robot.joints)
        .forEach(joint => {
          joint.ignoreLimits = ignore;
          joint.setJointValue(...joint.jointValue);
        });
    }
    if (dispatch) {
      this.dispatchEvent(new CustomEvent('ignore-limits-change', { bubbles: true, cancelable: true, composed: true }));
    }
  }
};
